package main

import (
	common "awesomeProject/kitex_gen/common"
	"awesomeProject/rpc/ticket_main_service/config"
	"awesomeProject/rpc/ticket_main_service/models"
	"context"
	"encoding/json"
	"fmt"
	"github.com/dgrijalva/jwt-go"
	"gorm.io/gorm"
	"time"
)

// 定义存储在 Redis 中的对象结构
type TokenPayload struct {
	Username string `json:"username"`
	UserID   int64  `json:"userId"`
	RoleID   int    `json:"roleId"`
}

// 自定义声明结构体
type CustomClaims struct {
	Username string `json:"username"`
	Role     uint8  `json:"role"`
	UserID   uint32 `json:"userId"`
	jwt.StandardClaims
}

// TicketMainServiceImpl implements the last service interface defined in the IDL.
type TicketMainServiceImpl struct{}

/*
PurchaseCrossRegion implements the TicketMainServiceImpl interface.

	跨地区购票系统
	检查座位可用性：验证座位在乘车日期内是否未被占用。
	锁定座位：将座位状态标记为“锁定”，防止其他用户同时购买。
	生成订单：插入订单记录并关联座位。
*/
func (s *TicketMainServiceImpl) PurchaseCrossRegion(ctx context.Context, request *common.PurchaseRequest) (resp *common.OrderDTO, err error) {
	// 验证并解析 JWT 令牌，提取用户信息
	claims, err := validateToken(request.AuthToken)
	if err != nil {
		return nil, fmt.Errorf("AUTH_ERROR: 无法验证用户身份: %w", err)
	}

	// 从 JWT 声明中提取 userID
	userID := claims.UserID

	startStationID := request.StartStationId
	endStationID := request.EndStationId
	seatIDs := request.SeatIds

	// 获取当前时间的 Unix 时间戳
	currentTime := time.Now().Unix()
	// 将 Unix 时间戳转换为 uint 类型
	orderID := uint(currentTime)

	// 解析 TravelDate（字符串转 time.Time）
	travelDate, err := time.Parse("2006-01-02", request.TravelDate)
	if err != nil {
		return nil, fmt.Errorf("INVALID_DATE: 无效的日期格式 %s", request.TravelDate)
	}

	// 初始化订单记录
	order := models.Order{
		OrderID:       orderID,
		UserID:        uint(userID),
		Status:        "已锁定",
		CreatedAt:     time.Now(),
		UpdatedAt:     time.Now(),
		PaymentMethod: "", // 支付方式可为空，后续支付时更新
		PaymentID:     "", // 第三方支付平台交易号可为空
	}

	// 插入订单记录
	err = DB.Create(&order).Error
	if err != nil {
		return nil, err // 插入订单失败返回错误
	}

	// 遍历每个 seatID，处理座位锁定和关联记录
	for _, seatID := range seatIDs {
		// 步骤1: 检查座位是否可用（需检查区间冲突）
		var occupancyID uint
		err = DB.Raw(`
			SELECT 
				so.occupancy_id 
			FROM 
				seat_occupancy so 
			WHERE 
				so.seat_id = ? 
				AND so.date = ? 
				AND (
					-- 检查区间重叠：新购票区间与已占区间是否冲突
					(so.start_station_id < ? AND so.end_station_id > ?)
					OR
					(so.status IN ('锁定', '已售'))
				);
		`, seatID, travelDate, endStationID, startStationID).Scan(&occupancyID).Error

		if err != nil {
			return nil, err // 查询失败返回错误
		}

		if occupancyID != 0 {
			// 如果查询结果非空，说明座位不可用，抛出 SEAT_UNAVAILABLE 异常
			return nil, fmt.Errorf("SEAT_UNAVAILABLE: 座位ID %d 已被占用或锁定", seatID)
		}

		// 步骤2: 锁定座位（使用乐观锁）
		var currentVersion int
		err = DB.Model(&models.SeatOccupancy{}).
			Where("seat_id = ? AND date = ?", seatID, travelDate).
			Select("version").
			Scan(&currentVersion).Error

		if err != nil {
			return nil, err // 查询版本号失败返回错误
		}

		result := DB.Model(&models.SeatOccupancy{}).
			Where("seat_id = ? AND date = ? AND version = ?", seatID, travelDate, currentVersion).
			Updates(map[string]interface{}{
				"status":   "锁定",
				"user_id":  userID,
				"order_id": orderID,
				"version":  gorm.Expr("version + 1"),
			})

		if result.RowsAffected == 0 {
			// 若影响行数为0，说明并发冲突，抛出 CONCURRENCY_CONFLICT 异常
			return nil, fmt.Errorf("CONCURRENCY_CONFLICT: 并发锁定失败，请重试")
		}

		// 步骤3: 插入订单座位关联记录
		orderSeat := models.OrderSeat{
			OrderID:        orderID,
			SeatID:         uint(seatID),
			StartStationID: uint(startStationID),
			EndStationID:   uint(endStationID),
			TravelDate:     travelDate,
		}

		err = DB.Create(&orderSeat).Error
		if err != nil {
			return nil, err // 插入订单座位关联记录失败返回错误
		}
	}

	// 返回订单信息
	resp = &common.OrderDTO{
		OrderId:   orderID,
		UserId:    int64(userID),
		Status:    "已锁定",
		CreatedAt: time.Now(),
	}

	return resp, nil
}

// RefundCrossRegion implements the TicketMainServiceImpl interface.
/* 用于火车票务系统的退票功能。
验证订单有效性：确保订单存在且属于当前用户。
释放座位占用：将座位状态从“已售”重置为“空闲”。
更新订单状态：标记订单为已退票。*/
func (s *TicketMainServiceImpl) RefundCrossRegion(ctx context.Context, request *common.RefundRequest) (resp *common.OrderDTO, err error) {
	// 从请求中提取参数
	claims, err := validateToken(request.AuthToken)
	if err != nil {
		return nil, fmt.Errorf("AUTH_ERROR: 无法验证用户身份: %w", err)
	}

	// 从 JWT 声明中提取 userID
	userID := claims.UserID

	orderID := request.OrderId
	seatIDs := request.SeatIds

	// 步骤1: 获取订单关联的座位信息
	var seatOccupancies []struct {
		OccupancyID uint
		SeatID      uint
		Version     int
	}
	err = DB.Raw(`
		SELECT 
			so.occupancy_id, 
			so.seat_id, 
			so.version 
		FROM 
			seat_occupancy so 
		WHERE 
			so.order_id = ? 
			AND so.user_id = ? 
			AND so.status = '已售';
	`, orderID, userID).Scan(&seatOccupancies).Error

	if err != nil {
		return nil, err // 查询失败返回错误
	}

	if len(seatOccupancies) == 0 {
		// 如果结果为空，说明没有找到对应的座位记录
		return nil, fmt.Errorf("TICKET_NOT_FOUND: 订单ID %s 的座位记录未找到", orderID)
	}

	// 提取座位ID和版本号
	seatIDMap := make(map[uint]int)
	for _, occupancy := range seatOccupancies {
		seatIDMap[occupancy.SeatID] = occupancy.Version
	}

	// 检查请求中的座位ID是否都存在于查询结果中
	for _, seatID := range seatIDs {
		if _, exists := seatIDMap[uint(seatID)]; !exists {
			return nil, fmt.Errorf("INVALID_SEAT: 座位ID %d 不属于订单 %s", seatID, orderID)
		}
	}

	// 步骤2: 释放座位（批量处理）
	for _, seatID := range seatIDs {
		currentVersion := seatIDMap[uint(seatID)]
		result := DB.Model(&models.SeatOccupancy{}).
			Where("seat_id = ? AND date = ? AND version = ?", seatID, time.Now().Format("2006-01-02"), currentVersion).
			Updates(map[string]interface{}{
				"status":   "空闲",
				"user_id":  nil,
				"order_id": nil,
				"version":  gorm.Expr("version + 1"),
			})

		if result.RowsAffected == 0 {
			// 若影响行数为0，说明并发冲突，抛出 CONCURRENCY_CONFLICT 异常
			return nil, fmt.Errorf("CONCURRENCY_CONFLICT: 并发释放座位失败，请重试")
		}
	}

	// 步骤3: 更新订单状态
	err = DB.Model(&models.Order{}).
		Where("order_id = ?", orderID).
		Updates(map[string]interface{}{
			"status":     "已退票",
			"updated_at": time.Now(),
		}).Error

	if err != nil {
		return nil, err // 更新订单状态失败返回错误
	}

	// 返回更新后的订单信息
	resp = &common.OrderDTO{
		OrderId: uint(orderID),
		UserId:  int64(userID),
		Status:  "已退票",
	}

	return resp, nil
}

// GetSeatsByTrainID 分页获取指定车次的所有座位信息
func (s *TicketMainServiceImpl) GetSeatsByTrainID(ctx context.Context, trainID uint, page int) (resp []*common.SeatDTO, err error) {

	// 计算分页参数
	pageSize := 20
	offset := (page - 1) * pageSize

	// 构建查询
	var seats []struct {
		SeatID         uint
		TrainID        uint
		CarriageNumber string
		SeatNumber     string
		SeatType       string
		StartStationID uint
		EndStationID   uint
		Date           string
		UserID         uint
		OrderID        string
		Status         string
	}

	err = DB.Table("seat_occupancy so").
		Select(`
			so.seat_id,
			so.train_id,
			s.carriage_number,
			s.seat_number,
			s.seat_type,
			so.start_station_id,
			so.end_station_id,
			so.date,
			so.user_id,
			so.order_id,
			so.status
		`).
		Joins("JOIN seats s ON so.seat_id = s.seat_id").
		Where("so.train_id = ?", trainID).
		Offset(offset).
		Limit(pageSize).
		Scan(&seats).Error

	if err != nil {
		return nil, err // 查询失败返回错误
	}

	// 将查询结果转换为 SeatDTO 列表
	resp = make([]*common.SeatDTO, len(seats))
	for i, seat := range seats {
		resp[i] = &common.SeatDTO{
			SeatId:         int32(seat.SeatID),
			TrainNumber:    fmt.Sprintf("%d", seat.TrainID), // 假设 TrainNumber 是字符串类型
			CarriageNumber: seat.CarriageNumber,
			SeatNumber:     seat.SeatNumber,
			SeatType:       seat.SeatType,
			SeatPrice:      0, // SeatPrice 在 SeatDTO 中存在但未在查询中使用，可以设置为 0 或从其他地方获取
			StartStationID: int32(seat.StartStationID),
			EndStationID:   int32(seat.EndStationID),
			TravelDate:     seat.Date,
			UserId:         int32(seat.UserID),
			OrderID:        seat.OrderID,
			Status:         seat.Status,
		}
	}

	return resp, nil
}

// 从 config.go 加载密钥并解析 username、role 和 userId
func validateToken(token string) (*CustomClaims, error) {
	ctx := context.Background()

	// 从配置文件中加载 JWT 密钥
	cfg, err := config.LoadConfig("config.yaml")
	if err != nil {
		return nil, fmt.Errorf("CONFIG_ERROR: 无法加载配置文件: %w", err)
	}

	// 检查 Redis 缓存中是否已存在该令牌
	cachedData, err := RedisClient.Get(ctx, token).Result()
	if err == nil {
		// 如果缓存命中，反序列化数据并返回
		var claims CustomClaims
		err := json.Unmarshal([]byte(cachedData), &claims)
		if err == nil {
			return &claims, nil
		}
	}

	// 解析 JWT 令牌
	parsedToken, err := jwt.ParseWithClaims(token, &CustomClaims{}, func(token *jwt.Token) (interface{}, error) {
		// 使用配置文件中的密钥进行验证
		return []byte(cfg.JWT.Secret), nil
	})

	if err != nil || !parsedToken.Valid {
		return nil, fmt.Errorf("INVALID_TOKEN: 无效的 JWT 令牌")
	}

	// 提取自定义声明信息
	claims, ok := parsedToken.Claims.(*CustomClaims)
	if !ok {
		return nil, fmt.Errorf("INVALID_CLAIMS: 无法解析 JWT 声明")
	}

	// 序列化对象为 JSON
	claimsJSON, err := json.Marshal(claims)
	if err != nil {
		return nil, fmt.Errorf("SERIALIZATION_ERROR: 序列化失败")
	}

	// 存入 Redis，设置过期时间为 1 小时
	err = RedisClient.Set(ctx, token, claimsJSON, time.Hour).Err()
	if err != nil {
		return nil, fmt.Errorf("REDIS_ERROR: 无法存储到 Redis")
	}

	return claims, nil
}

// ForceSync implements the TicketMainServiceImpl interface.
func (s *TicketMainServiceImpl) ForceSync(ctx context.Context, request *common.SyncRequest) (err error) {
	// TODO: Your code here...
	return
}

// ManageSeats implements the TicketMainServiceImpl interface.
func (s *TicketMainServiceImpl) ManageSeats(ctx context.Context, trainNumber string, operationType string, seatIds []int32, adminToken string) (resp []*common.SeatDTO, err error) {
	// TODO: Your code here...
	return
}
